/*
 * @(#)MathG.java
 *
 * $Date: 2012-07-03 01:10:05 -0500 (Tue, 03 Jul 2012) $
 *
 * Copyright (c) 2011 by Jeremy Wood.
 * All rights reserved.
 *
 * The copyright of this software is owned by Jeremy Wood.
 * You may not use, copy or modify this software, except in
 * accordance with the license agreement you entered into with
 * Jeremy Wood. For details see accompanying license terms.
 *
 * This software is probably, but not necessarily, discussed here:
 * http://javagraphics.java.net/
 *
 * That site should also contain the most recent official version
 * of this software.  (See the SVN repository for more details.)
 */
package com.bric.math;

import com.bric.math.function.Function;
import com.bric.math.function.PiecewiseFunction;
import com.bric.math.function.PolynomialFunction;


/** This provides some alternative implementations of a few methods from
 * the Math class.
 * <P>This class may use approximations with various levels of error.  The "G"
 * in the name stands for "Graphics", because it was originally conceived
 * as a tool to speed up graphics.  When I iterate over every pixel in an image
 * to perform some operation: I don't really need the precision that the Math
 * class offers.
 * <P>Many thanks to Oleg E. for some insights regarding machine error and
 * design.
 * <P>See MathGDemo.java for a set of tests comparing the speed/accuracy
 * of java.lang.Math and com.bric.math.MathG.
 *
 */
public abstract class MathG {

    /** Finds the closest integer that is less than or equal to the argument as a double.
     * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10.
     */
    public static final double floorDouble(double d) {
        int id = (int)d;
        return d==id || d > 0 ? id : id-1;
    }

    /** Finds the closest integer that is less than or equal to the argument as an int.
     * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10.
     */
    public static final int floorInt(double d) {
        int id = (int)d;
        return d==id || d > 0 ? id : id-1;
    }

    /** Rounds a double to the nearest integer value.
     * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10.
     */
    public static final int roundInt(double d) {
        int i;
        if(d>=0) {
            i = (int)(d+.5);
        } else {
            i = (int)(d-.5);
        }
        return i;
    }

    /** Rounds a double to the nearest integer value.
     * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10.
     */
    public static final double roundDouble(double d) {
        int i;
        if(d>=0) {
            i = (int)(d+.5);
        } else {
            i = (int)(d-.5);
        }
        return i;
    }

    /** Finds the closest integer that is greater than or equal to the argument as an int.
     * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10.
     */
    public static final int ceilInt(double d) {
        int id = (int)d;
        return d==id || d < 0 ? id : -((int)(-d))+1;
    }

    /** Finds the closest integer that is greater than or equal to the argument as a double.
     * <BR>Warning: do not use an argument greater than 1e10, or less than 1e-10.
     */
    public static final double ceilDouble(double d) {
        int id = (int)d;
        return d==id || d < 0 ? id : -((int)(-d))+1;
    }

    private static final double PI = Math.PI;
    private static final double TWO_PI = 2.0*Math.PI;
    private static final double PI_OVER_2 = Math.PI/2.0;

    private static Function sinFunction01 = PolynomialFunction.createFit(
            new double[] { 0, Math.PI/2},
            new double[] { Math.sin(0), Math.sin(Math.PI/2)},
            new double[] { Math.cos(0), Math.cos(Math.PI/2)} );

    private static Function sinFunction00004 = PolynomialFunction.createFit(
            new double[] { 0, Math.PI/4, Math.PI/2},
            new double[] { Math.sin(0), Math.sin(Math.PI/4), Math.sin(Math.PI/2)},
            new double[] { Math.cos(0), Math.cos(Math.PI/4), Math.cos(Math.PI/2)} );

    private static Function acosFunction;

    //define the acosFunction:
    static {
        Function acos = new Function() {
            public double evaluate(double x) {
                return Math.acos(x);
            }
            public double[] evaluateInverse(double y) {
                throw new UnsupportedOperationException();
            }
        };
        Function acosD = new Function() {
            public double evaluate(double x) {
                return -1.0/Math.sqrt(1-x*x);
            }
            public double[] evaluateInverse(double y) {
                throw new UnsupportedOperationException();
            }
        };

        PiecewiseFunction p = PiecewiseFunction.create(acos, acosD, 0, 1, 512);
        p.setFunction(p.getFunctionCount()-1,
                PiecewiseFunction.create(acos, acosD,
                1.0-1.0/(p.getFunctionCount()),
                1, 64));
        acosFunction = p;
    }

    /** Returns an approximate value of the sin(v) that should be
     * within plus-or-minus .0108 of the value returned by Math.sin().
     * <P>If the argument is greater in magnitude than 1e10, then
     * this delegates to Math.sin().
     *
     * @param v
     * @return an approximate value of sin(v)
     */
    public static final double sin01(double v) {
        /** This is exactly the same code as sin00004, except it
         * uses the smaller polynomial.  I avoided refactoring
         * the code to use a common method in the theory that
         * avoiding adding a method to the stack trace may
         * shave off just a tiny bit of performance.  Normally
         * this would be excessive, but my goal is optimum
         * performance in really tight loops: every line counts!
         */
        double finalMultiplier;
        if(v<0) {
            finalMultiplier = -1;
            v = -v;
        } else {
            finalMultiplier = 1;
        }

        if(v>1.0E10) {
            if(printedOverflowError==false) {
                printedOverflowError = true;
                System.err.println("Warning: MathG is not designed to estimate the sine of values of 1.0e10.  Math.sin() will be used, which may result in slower performance.");
            }
            return finalMultiplier*Math.sin(v);
        } else if(v<.01) {
            //if we're that small, then y=sin(x) -> y=x
            //sin(.01)-.01 = -1.6666583333574403E-7
            return v*finalMultiplier;
        }

        if(v>TWO_PI) {
            //v = v%TWO_PI;
            long m = (long)(v/TWO_PI);
            v = v-m*TWO_PI;
        }
        if(v>PI) {
            v = v-PI;
            finalMultiplier = -finalMultiplier;
        }
        if(v>PI_OVER_2) {
            v = PI-v;
        }


        double result = sinFunction01.evaluate(v);
        result = result*finalMultiplier;

        return result;
    }

    /** Controls whether an error message has been printed to
     * System.err. yet this session regarding calling
     * calculateSin() on values that are too large.
     */
    private static boolean printedOverflowError = false;

    /** Returns an approximate value of the cos(v) that should be
     * within plus-or-minus .0108 of the value returned by Math.cos().
     * <P>If the argument is greater in magnitude than 1e10, then
     * this delegates to Math.cos().
     *
     * @param v
     * @return an approximate value of cos(v)
     */
    public static final double cos01(double v) {
        if(v>1e10 || v<1e-10)
            return Math.cos(v);
        return sin01(v-PI_OVER_2);
    }

    /** Returns an approximate value of the sin(v) that should be
     * within plus-or-minus .00004 of the value returned by Math.sin().
     * <P>If the argument is greater in magnitude than 1e10, then
     * this delegates to Math.sin().
     *
     * @param v
     * @return an approximate value of sin(v)
     */
    public static final double sin00004(double v) {
        double finalMultiplier;
        if(v<0) {
            finalMultiplier = -1;
            v = -v;
        } else {
            finalMultiplier = 1;
        }

        if(v<.01) {
            //if we're that small, then y=sin(x) -> y=x
            //sin(.01)-.01 = -1.6666583333574403E-7
            return v*finalMultiplier;
        } else if(v>1.0E10) {
            if(printedOverflowError==false) {
                printedOverflowError = true;
                System.err.println("Warning: MathG is not designed to estimate the sine of values of 1.0e10.  Math.sin() will be used, which may result in slower performance.");
            }
            return finalMultiplier*Math.sin(v);
        }

        if(v>TWO_PI) {
            //v = v%TWO_PI;
            long m = (long)(v/TWO_PI);
            v = v-m*TWO_PI;
        }
        if(v>PI) {
            v = v-PI;
            finalMultiplier = -finalMultiplier;
        }
        if(v>PI_OVER_2) {
            v = PI-v;
        }

        double result = sinFunction00004.evaluate(v);
        result = result*finalMultiplier;

        return result;
    }

    /** Returns an approximate value of the acos(v) that should be
     * within plus-or-minus .00004 of the value returned by Math.acos().
     *
     * @param v
     * @return an approximate value of acos(v)
     */
    public static final double acos(double v) {
        if(v<-1 || v>1) throw new IllegalArgumentException("v ("+v+") must be within [-1,1]");
        if(v<0) {
            v = -v;
            return Math.PI-acos(v);
        }
        return acosFunction.evaluate(v);
    }


    /** Returns an approximate value of the cos(v) that should be
     * within plus-or-minus .00004 of the value returned by Math.cos().
     * <P>If the argument is greater in magnitude than 1e10, then
     * this delegates to Math.cos().
     *
     * @param v
     * @return an approximate value of cos(v)
     */
    public static final double cos00004(double v) {
        if(v>1e10 || v<1e-10)
            return Math.cos(v);
        return sin00004(v-PI_OVER_2);
    }
}
